home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Tech Arsenal 1
/
Tech Arsenal (Arsenal Computer).ISO
/
tek-13
/
xvisrc.zip
/
FILEIO.C
< prev
next >
Wrap
C/C++ Source or Header
|
1992-07-28
|
15KB
|
648 lines
/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)fileio.c 2.1 (Chris & John Downey) 7/29/92";
#endif
/***
* program name:
xvi
* function:
PD version of UNIX "vi" editor, with extensions.
* module name:
fileio.c
* module function:
File i/o routines.
* history:
STEVIE - ST Editor for VI Enthusiasts, Version 3.10
Originally by Tim Thompson (twitch!tjt)
Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
Heavily modified by Chris & John Downey
***/
#include "xvi.h"
#ifdef MEGAMAX
overlay "fileio"
#endif
/*
* Definition of a text file format.
*
* This structure may need additional entries to cope with very strange file
* formats (such as VMS).
*/
struct tfformat
{
int tf_eolnchars[2]; /* end of line markers */
int tf_eofchar; /* end of file marker */
unsigned char tf_dynamic; /* autodetect format? */
};
/*
* Names of values for the P_format enumerated parameter.
*
* It is essential that these are in the same order as the fmt_...
* symbolic constants defined in xvi.h.
*/
char *fmt_strings[] = {
"cstring",
"macintosh",
"msdos",
"os2",
"qnx",
"tos",
"unix",
NULL,
};
/*
* Format structures.
*
* It is essential that these are in the same order as the fmt_...
* symbolic constants defined in xvi.h.
*
* We don't use '\r' or '\n' to define the end-of-line characters
* because some compilers interpret them differently & this code has
* to work the same on all systems.
*/
#define NOCHAR EOF
static const struct tfformat tftable [] = {
{ { '\0', NOCHAR }, EOF, FALSE }, /* fmt_CSTRING */
{ { CTRL('M'), NOCHAR }, EOF, FALSE }, /* fmt_MACINTOSH */
{ { CTRL('M'), CTRL('J') }, CTRL('Z'), TRUE }, /* fmt_MSDOS */
{ { CTRL('M'), CTRL('J') }, CTRL('Z'), TRUE }, /* fmt_OS2 */
{ { '\036', NOCHAR }, EOF, FALSE }, /* fmt_QNX */
{ { CTRL('M'), CTRL('J') }, EOF, TRUE }, /* fmt_TOS */
{ { CTRL('J'), NOCHAR }, EOF, FALSE } /* fmt_UNIX */
};
/*
* Index of last entry in tftable.
*/
#define TFMAX (sizeof tftable / sizeof (struct tfformat) - 1)
/*
* Current text file format.
*/
static struct tfformat curfmt = { { 0, 0 }, 0, FALSE };
#define eolnchars curfmt.tf_eolnchars
#define eofchar curfmt.tf_eofchar
/*
* Name of current text file format.
*/
static char *fmtname = "INTERNAL ERROR";
/*
* Copy the tftable entry indexed by tfindex into curfmt & update
* fmtname. Return FALSE if the parameter is invalid, otherwise TRUE.
*
* This is called from set_format() (below).
*
* Note that we copy a whole tfformat structure here, instead of just copying
* a pointer. This is so that curfmt.eolnchars & curfmt.eofchar will compile
* to absolute address references instead of indirections, which should be
* significantly more efficient because they are referenced for every
* character we read or write.
*/
static bool_t
txtformset(tfindex)
int tfindex;
{
if (tfindex < 0 || tfindex > TFMAX)
return FALSE;
(void) memcpy((char *) &curfmt, (const char *) &tftable[tfindex],
sizeof curfmt);
fmtname = fmt_strings[tfindex];
return TRUE;
}
/*
* Check value of P_format parameter.
*/
bool_t
set_format(window, new_value, interactive)
Xviwin *window;
Paramval new_value;
bool_t interactive;
{
if (!txtformset(new_value.pv_i)) {
if (interactive) {
show_error(window, "Invalid text file format (%d)",
new_value.pv_i);
}
return(FALSE);
}
return(TRUE);
}
/*
* Find out if there's a format we know about with the single specified
* end-of-line character. If so, change to it.
*/
static bool_t
eolnhack(c)
register int c;
{
register int tfindex;
for (tfindex = 0; tfindex <= TFMAX; tfindex++) {
register const int *eolp;
eolp = tftable[tfindex].tf_eolnchars;
if (eolp[0] == c && eolp[1] == NOCHAR) {
(void) txtformset(tfindex);
set_param(P_format, tfindex, (char **) NULL);
P_setchanged(P_format);
return TRUE;
}
}
return FALSE;
}
/*
* Read in the given file, filling in the given "head" and "tail"
* arguments with pointers to the first and last elements of the
* linked list of Lines; if nothing was read, both pointers are set to
* NULL. The return value is the number of lines read, if successful
* (this can be 0 for an empty file), or an error return code, which
* can be gf_NEWFILE, gf_CANTOPEN, gf_IOERR or gf_NOMEM.
*
* If there is an error, such as not being able to read the file or
* running out of memory, an error message is printed; otherwise, a
* statistics line is printed using show_message().
*
* The "extra_str" string is printed just after the filename in the
* displayed line, and is typically used for "Read Only" messages. If
* the file doesn't appear to exist, the filename is printed again,
* immediately followed by the "no_file_str" string, & we return
* gf_NEWFILE.
*/
long
get_file(window, filename, headp, tailp, extra_str, no_file_str)
Xviwin *window;
char *filename;
Line **headp;
Line **tailp;
char *extra_str;
char *no_file_str;
{
register FILE *fp; /* ptr to open file */
#ifndef i386
register
#endif
unsigned long nchars; /* number of chars read */
unsigned long nlines; /* number of lines read */
unsigned long nulls; /* number of null chars */
unsigned long toolong; /*
* number of lines
* which were too long
*/
bool_t incomplete; /* incomplete last line */
Line *lptr = NULL; /* pointer to list of lines */
Line *last = NULL; /*
* last complete line
* read in
*/
Line *lp; /*
* line currently
* being read in
*/
register enum {
at_soln,
in_line,
got_eolnc0,
at_eoln,
at_eof
} state;
register char *buff; /*
* text of line
* being read in
*/
register int col; /* current column in line */
if (P_ischanged(P_format)) {
show_message(window, "\"%s\" [%s]%s", filename, fmtname, extra_str);
} else {
show_message(window, "\"%s\"%s", filename, extra_str);
}
fp = fopenrb(filename);
if (fp == NULL) {
*headp = *tailp = NULL;
if (exists(filename)) {
show_error(window, "Can't read \"%s\"", filename);
return(gf_CANTOPEN);
} else {
show_message(window, "\"%s\"%s", filename, no_file_str);
return(gf_NEWFILE);
}
}
#ifdef SETVBUF_AVAIL
{
unsigned int bufsize;
bufsize = READBUFSIZ;
/*
* Keep trying to set the buffer size to something
* large, reducing the size by 1/2 each time.
* This will eventually work, and will not usually
* take very many calls. (jmd)
*/
while (setvbuf(fp, (char *) NULL, _IOFBF, bufsize) != 0 &&
bufsize > 1) {
bufsize /= 2;
}
}
#endif /* SETVBUF_AVAIL */
nchars = nlines = nulls = toolong = 0;
col = 0;
incomplete = FALSE;
state = at_soln;
while (state != at_eof) {
register int c;
c = getc(fp);
if (c == EOF || c == eofchar) {
if (state != at_soln) {
/*
* Reached EOF in the middle of a line; what
* we do here is to pretend we got a properly
* terminated line, and assume that a
* subsequent getc will still return EOF.
*/
incomplete = TRUE;
state = at_eoln;
} else {
state = at_eof;
break;
}
} else {
nchars++;
switch (state) {
case at_soln:
/*
* We're at the start of a line, &
* we've got at least one character,
* so we have to allocate a new Line
* structure.
*
* If we can't do it, we throw away
* the lines we've read in so far, &
* return gf_NOMEM.
*/
if ((lp = newline(MAX_LINE_LENGTH)) == NULL) {
if (lptr != NULL) {
throw(lptr);
}
(void) fclose(fp);
*headp = *tailp = NULL;
return(gf_NOMEM);
} else {
buff = lp->l_text;
}
case in_line:
if (c == eolnchars[0]) {
if (eolnchars[1] == NOCHAR) {
state = at_eoln;
} else {
state = got_eolnc0;
continue;
}
} else if (c == eolnchars [1] && curfmt.tf_dynamic &&
eolnhack(c)) {
/*
* If we get the second end-of-line
* marker, but not the first, see if
* we can accept the second one by
* itself as an end-of-line.
*/
state = at_eoln;
}
break;
case got_eolnc0:
if (c == eolnchars[1]) {
state = at_eoln;
} else if (curfmt.tf_dynamic && eolnhack(eolnchars[0])) {
/*
* If we get the first end-of-line
* marker, but not the second, see
* if we can accept the first one
* by itself as an end-of-line.
*/
(void) ungetc(c, fp);
state = at_eoln;
} else {
/*
* We can't. Just take the first one
* literally.
*/
state = in_line;
(void) ungetc(c, fp);
c = eolnchars [0];
}
}
}
if (state == at_eoln || col >= MAX_LINE_LENGTH - 1) {
/*
* First null-terminate the old line.
*/
buff[col] = '\0';
/*
* If this fails, we squeak at the user and
* then throw away the lines read in so far.
*/
buff = realloc(buff, (unsigned) col + 1);
if (buff == NULL) {
if (lptr != NULL)
throw(lptr);
(void) fclose(fp);
*headp = *tailp = NULL;
return gf_NOMEM;
}
lp->l_text = buff;
lp->l_size = col + 1;
/*
* Tack the line onto the end of the list,
* and then point "last" at it.
*/
if (lptr == NULL) {
lptr = lp;
last = lptr;
} else {
last->l_next = lp;
lp->l_prev = last;
last = lp;
}
nlines++;
col = 0;
if (state != at_eoln) {
toolong++;
/*
* We didn't get a properly terminated line,
* but we still have to do something with the
* character we've read.
*/
(void) ungetc(c, fp);
}
state = at_soln;
} else {
/*
* Nulls are special; they can't show up in the file.
*/
if (c == '\0') {
nulls++;
continue;
}
state = in_line;
buff[col++] = c;
}
}
(void) fclose(fp);
{
/*
* Assemble error messages for status line.
*/
Flexbuf errbuf;
char *errs;
flexnew(&errbuf);
if (nulls > 0) {
(void) lformat(&errbuf, " (%ld null character%s)",
nulls, (nulls == 1 ? "" : "s"));
}
if (toolong > 0) {
(void) lformat(&errbuf, " (%ld line%s too long)",
toolong, (toolong == 1 ? "" : "s"));
}
if (incomplete) {
(void) lformat(&errbuf, " (incomplete last line)");
}
/*
* Show status line.
*/
errs = flexgetstr(&errbuf);
if (P_ischanged(P_format)) {
show_message(window, "\"%s\" [%s]%s %ld/%ld%s",
filename, fmtname, extra_str,
nlines, nchars, errs);
} else {
show_message(window, "\"%s\"%s %ld/%ld%s",
filename, extra_str, nlines, nchars, errs);
}
flexdelete(&errbuf);
}
*headp = lptr;
*tailp = last;
return(nlines);
}
/*
* writeit - write to file 'fname' lines 'start' through 'end'
*
* If either 'start' or 'end' are NULL, the default
* is to use the start or end of the file respectively.
*
* Unless the "force" argument is TRUE, we do not write
* out buffers which have the "readonly" flag set.
*/
bool_t
writeit(window, fname, start, end, force)
Xviwin *window;
char *fname;
Line *start, *end;
bool_t force;
{
FILE *fp;
unsigned long nc;
unsigned long nl;
Buffer *buffer;
buffer = window->w_buffer;
if (is_readonly(buffer) && !force) {
show_error(window, "\"%s\" File is read only", fname);
return(FALSE);
}
show_message(window,
(P_ischanged(P_format) ? "\"%s\" [%s]" : "\"%s\""),
fname, fmtname);
/*
* Preserve the buffer here so if the write fails it will at
* least have been saved.
*/
if (!preservebuf(window)) {
return(FALSE);
}
if (!can_write(fname)) {
show_error(window, "\"%s\" Permission denied", fname);
return(FALSE);
}
fp = fopenwb(fname);
if (fp == NULL) {
show_error(window, "Can't write \"%s\"", fname);
return(FALSE);
}
if (put_file(window, fp, start, end, &nc, &nl) == FALSE) {
return(FALSE);
}
if (P_ischanged(P_format)) {
show_message(window, "\"%s\" [%s] %ld/%ld", fname, fmtname, nl, nc);
} else {
show_message(window, "\"%s\" %ld/%ld", fname, nl, nc);
}
/*
* Make sure any preserve file is removed if it isn't wanted.
* It's not worth checking for the file's existence before
* trying to remove it; the remove() will do the check anyway.
*/
if (Pn(P_preserve) < psv_PARANOID) {
if (buffer->b_tempfname != NULL) {
(void) remove(buffer->b_tempfname);
}
}
/*
* If no start and end lines were specified, or they
* were specified as the start and end of the buffer,
* and we wrote out the whole file, then we can clear
* the modified status. This must be safe.
*/
if ((start == NULL || start == buffer->b_file) &&
(end == NULL || end == buffer->b_lastline->l_prev)) {
buffer->b_flags &= ~FL_MODIFIED;
}
return(TRUE);
}
/*
* Write out the buffer between the given two line pointers
* (which default to start and end of buffer) to the given file
* pointer. The reference parameters ncp and nlp are filled in
* with the number of characters and lines written to the file.
* The return value is TRUE for success, FALSE for all kinds of
* failure.
*/
bool_t
put_file(window, f, start, end, ncp, nlp)
Xviwin *window;
register FILE *f;
Line *start, *end;
unsigned long *ncp, *nlp;
{
register Line *lp;
register unsigned long nchars;
unsigned long nlines;
Buffer *buffer;
buffer = window->w_buffer;
#ifdef SETVBUF_AVAIL
{
unsigned int bufsize = WRTBUFSIZ;
/*
* Keep trying to set the buffer size to something
* large, reducing the size by 1/2 each time.
* This will eventually work, and will not usually
* take very many calls. (jmd)
*/
while (setvbuf(f, (char *) NULL, _IOFBF, bufsize) != 0 &&
bufsize > 1) {
bufsize /= 2;
}
}
#endif /* SETVBUF_AVAIL */
/*
* If we were given a bound, start there. Otherwise just
* start at the beginning of the file.
*/
if (start == NULL) {
lp = buffer->b_file;
} else {
lp = start;
}
nlines = 0;
nchars = 0;
for ( ; lp != buffer->b_lastline; lp = lp->l_next) {
register char *cp;
/*
* Write out the characters which comprise the line.
* Register declarations are used for all variables
* which form a part of this loop, in order to make
* it as fast as possible.
*/
for (cp = lp->l_text; *cp != '\0'; cp++) {
putc(*cp, f);
nchars++;
}
putc(eolnchars[0], f);
nchars++;
if (eolnchars[1] != NOCHAR) {
putc(eolnchars[1], f);
nchars++;
}
if (ferror(f)) {
(void) fclose(f);
return(FALSE);
}
nlines++;
/*
* If we were given an upper bound, and we
* just did that line, then bag it now.
*/
if (end != NULL) {
if (end == lp)
break;
}
}
if (fclose(f) != 0) {
return(FALSE);
}
/*
* Success!
*/
if (ncp != NULL)
*ncp = nchars;
if (nlp != NULL)
*nlp = nlines;
return(TRUE);
}